﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Net.Sockets;
using System.Net;
using System.Text;
using System.Threading; 

namespace WinScanInputSimulate
{

    /// <summary>
    /// 异步TcpClient客户端通信。
    /// 可以设置失败重试通信，
    /// </summary>
    public class AsyncTcpClient : IDisposable
    {
        private TcpClient tcpClient;

        private bool disposed = false;

        private int retries = 0;

        public string laststr = "";

        /// <summary>
        /// 是否已连接
        /// </summary>
        public bool Connected
        {
            get
            {
                if (tcpClient == null || tcpClient.Client == null)
                {
                    return false;
                }
                return tcpClient.Client.Connected;
            }
        }

        /// <summary>
        /// IP地址
        /// </summary>
        public IPAddress Addresses { get; private set; }
        /// <summary>
        /// 端口号
        /// </summary>
        public int Port { get; private set; }
        /// <summary>
        /// 重试次数
        /// </summary>
        public int Retries { get; set; }

        /// <summary>
        /// 重试间隔时间
        /// </summary>
        public int RetryInterval { get; set; }

        /// <summary>
        /// 连接的IP与端口
        /// </summary>
        public IPEndPoint RemoteIPEndPoint => new IPEndPoint(Addresses, Port);
        /// <summary>
        /// 编码方式
        /// </summary>
        public Encoding Encoding { get; set; }

        public event EventHandler<TcpDatagramReceivedEventArgs<byte[]>> DatagramReceived;

        /// <summary>
        /// 接收明文事件
        /// </summary>
        public event EventHandler<TcpDatagramReceivedEventArgs<string>> PlaintextReceived;
        /// <summary>
        /// 连接成功事件
        /// </summary>
        public event EventHandler<TcpServerConnectedEventArgs> ServerConnected;
        /// <summary>
        /// 断开连接事件
        /// </summary>
        public event EventHandler<TcpServerDisconnectedEventArgs> ServerDisconnected;
        /// <summary>
        /// 连接异常事件
        /// </summary>
        public event EventHandler<TcpServerExceptionOccurredEventArgs> ServerExceptionOccurred;

        /// <summary>
        /// 异步TcpClient客户端通信
        /// 可以设置失败重试通信，
        /// </summary>
        /// <param name="remoteIPAddress">IP地址</param>
        /// <param name="remotePort">端口号</param>
        public AsyncTcpClient(IPAddress remoteIPAddress, int remotePort)
        {
            Addresses = remoteIPAddress;
            Port = remotePort;
            Encoding = Encoding.Default;
            Retries = 3;
            RetryInterval = 5;
        }

        /// <summary>
        /// 连接
        /// </summary>
        /// <returns></returns>
        public AsyncTcpClient Connect()
        {
            tcpClient = new TcpClient();
            if (!Connected)
            {
                tcpClient.BeginConnect(Addresses, Port, HandleTcpServerConnected, tcpClient);
            }
            return this;
        }
        /// <summary>
        /// 断开
        /// </summary>
        /// <returns></returns>
        public AsyncTcpClient Close()
        {
            if (Connected)
            {
                retries = 0;
                tcpClient.Close();
                RaiseServerDisconnected(Addresses, Port);
            }
            return this;
        }

        /// <summary>
        /// 连接成功后回调处理
        /// </summary>
        /// <param name="ar"></param>
        private void HandleTcpServerConnected(IAsyncResult ar)
        {
            try
            {
                tcpClient.EndConnect(ar);
                RaiseServerConnected(Addresses, Port);
                retries = 0;
            }
            catch (Exception ex)
            {
                //LogHelper.Exception(ex, "HandleTcpServerConnected");
                if (Retries > 0)
                {
                    if (retries > 0)
                    {
                        Trace.WriteLine(string.Format(CultureInfo.InvariantCulture, "Connect to server with retry {0} failed.", new object[1] { retries }));
                    }
                    retries++;
                    if (retries > Retries)
                    {
                        RaiseServerExceptionOccurred(Addresses, Port, ex);
                        return;
                    }
                }
                Thread.Sleep(TimeSpan.FromSeconds(RetryInterval));
                Connect();
                return;
            }
            byte[] buffer = new byte[tcpClient.ReceiveBufferSize];
            tcpClient.GetStream().BeginRead(buffer, 0, buffer.Length, HandleDatagramReceived, buffer);
        }

        private void HandleDatagramReceived(IAsyncResult ar)
        {
            try
            {
                NetworkStream stream = tcpClient.GetStream();
                int numberOfReadBytes = 0;
                try
                {
                    numberOfReadBytes = stream.EndRead(ar);
                }
                catch
                {
                    numberOfReadBytes = 0;
                }
                if (numberOfReadBytes == 0)
                {
                    Close();
                    Trace.WriteLine("连接断开,尝试重连...");
                    Connect();
                    return;
                }
                byte[] buffer = (byte[])ar.AsyncState;
                byte[] receivedBytes = new byte[numberOfReadBytes];
                Buffer.BlockCopy(buffer, 0, receivedBytes, 0, numberOfReadBytes);
                string res = "";
                ByteArrayToHexStr(ref receivedBytes, ref res, Reverse: false);
                RaiseDatagramReceived(tcpClient, receivedBytes);
                RaisePlaintextReceived(tcpClient, receivedBytes);
                stream.BeginRead(buffer, 0, buffer.Length, HandleDatagramReceived, buffer);
            }
            catch (Exception ex)
            {
                Trace.WriteLine(ex.Message);
            }
        }

        /// <summary>
        /// 字节内容转为字符串内容
        /// </summary>
        /// <param name="ByteArray"></param>
        /// <param name="hexStr"></param>
        /// <param name="Reverse">是否反转掉换（高位在前，低位在后）</param>
        public void ByteArrayToHexStr(ref byte[] ByteArray, ref string hexStr, bool Reverse)
        {
            try
            {
                int LenofArray = ByteArray.Length;
                string str = "";
                hexStr = "";
                int Size = LenofArray;
                if (Reverse)
                {
                    for (int i = 0; i < Size; i++)
                    {
                        string ByteStr = "";
                        ByteStr = Convert.ToString(ByteArray[i], 16).ToUpper();
                        ByteStr = new string('0', 2 - ByteStr.Length) + ByteStr;
                        str = ByteStr + str;
                    }
                }
                else
                {
                    for (int i = 0; i < Size; i++)
                    {
                        string ByteStr = "";
                        ByteStr = Convert.ToString(ByteArray[i], 16).ToUpper();
                        ByteStr = new string('0', 2 - ByteStr.Length) + ByteStr;
                        str += ByteStr;
                    }
                }
                hexStr = str;
            }
            catch (Exception)
            {
                int LenofArray = ByteArray.Length;
                for (int i = 0; i < LenofArray * 2; i++)
                {
                    hexStr += "F";
                }
            }
        }

        private void RaiseDatagramReceived(TcpClient sender, byte[] datagram)
        {
            if (this.DatagramReceived != null)
            {
                this.DatagramReceived(this, new TcpDatagramReceivedEventArgs<byte[]>(sender, datagram));
            }
        }

        private void RaisePlaintextReceived(TcpClient sender, byte[] datagram)
        {
            if (this.PlaintextReceived != null)
            {
                this.PlaintextReceived(this, new TcpDatagramReceivedEventArgs<string>(sender, Encoding.GetString(datagram, 0, datagram.Length)));
            }
        }

        private void RaiseServerConnected(IPAddress ipAddresses, int port)
        {
            if (this.ServerConnected != null)
            {
                this.ServerConnected(this, new TcpServerConnectedEventArgs(ipAddresses, port));
            }
        }

        private void RaiseServerDisconnected(IPAddress ipAddresses, int port)
        {
            if (this.ServerDisconnected != null)
            {
                this.ServerDisconnected(this, new TcpServerDisconnectedEventArgs(ipAddresses, port));
            }
        }

        private void RaiseServerExceptionOccurred(IPAddress ipAddresses, int port, Exception innerException)
        {
            if (this.ServerExceptionOccurred != null)
            {
                this.ServerExceptionOccurred(this, new TcpServerExceptionOccurredEventArgs(ipAddresses, port, innerException));
            }
        }

        public void Send(byte[] datagram)
        {
            if (datagram == null)
            {
                throw new ArgumentNullException("datagram");
            }
            if (!Connected)
            {
                RaiseServerDisconnected(Addresses, Port);
                throw new InvalidProgramException("This client has not connected to server.");
            }
            tcpClient.GetStream().BeginWrite(datagram, 0, datagram.Length, HandleDatagramWritten, tcpClient);
        }

        private void HandleDatagramWritten(IAsyncResult ar)
        {
            ((TcpClient)ar.AsyncState).GetStream().EndWrite(ar);
        }

        public void Send(string datagram)
        {
            try
            {
                byte[] temp = new byte[datagram.Length / 2];
                if (Connected)
                {
                    Send(temp);
                }
            }
            catch (Exception)
            {
                throw;
            }
        }

        public void Dispose()
        {
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposed)
            {
                return;
            }
            if (disposing)
            {
                try
                {
                    Close();
                    if (tcpClient != null)
                    {
                        tcpClient = null;
                    }
                }
                catch
                {
                }
            }
            disposed = true;
        }
    }
    public class TcpDatagramReceivedEventArgs<T> : EventArgs
    {
        public TcpClient TcpClient { get; private set; }

        public T Datagram { get; private set; }

        public TcpDatagramReceivedEventArgs(TcpClient tcpClient, T datagram)
        {
            TcpClient = tcpClient;
            Datagram = datagram;
        }
    }

    public class TcpServerExceptionOccurredEventArgs : EventArgs
    {
        public IPAddress Address { get; private set; }

        public int Port { get; private set; }

        public Exception Exception { get; private set; }

        public TcpServerExceptionOccurredEventArgs(IPAddress ipAddresses, int port, Exception innerException)
        {
            if (ipAddresses == null)
            {
                throw new ArgumentNullException("ipAddress");
            }
            Address = ipAddresses;
            Port = port;
            Exception = innerException;
        }

        public override string ToString()
        {
            return Address?.ToString() + ":" + Port.ToString(CultureInfo.InvariantCulture);
        }
    }
    public class TcpServerDisconnectedEventArgs : EventArgs
    {
        public IPAddress Address { get; private set; }

        public int Port { get; private set; }

        public TcpServerDisconnectedEventArgs(IPAddress ipAddress, int port)
        {
            if (ipAddress == null)
            {
                throw new ArgumentNullException("ipAddress");
            }
            Address = ipAddress;
            Port = port;
        }

        public override string ToString()
        {
            return Address?.ToString() + ":" + Port.ToString(CultureInfo.InvariantCulture);
        }
    }

    public class TcpServerConnectedEventArgs : EventArgs
    {
        public IPAddress Address { get; private set; }

        public int Port { get; private set; }

        public TcpServerConnectedEventArgs(IPAddress ipAddress, int port)
        {
            if (ipAddress == null)
            {
                throw new ArgumentNullException("ipAddress");
            }
            Address = ipAddress;
            Port = port;
        }

        public override string ToString()
        {
            return Address?.ToString() + ":" + Port.ToString(CultureInfo.InvariantCulture);
        }
    }
}
